package com.bw.itext.pdf.service;

import cn.hutool.core.bean.BeanUtil;
import com.bw.itext.pdf.PdfProperties;
import com.bw.itext.pdf.annotation.PdfValue;
import com.bw.itext.pdf.utils.DeferredSignPdfAssLry;
import com.bw.itext.pdf.utils.EmbdedSignatureContainer;
import com.bw.itext.pdf.utils.ExternalEmptySignatureContainer;
import com.bw.itext.pdf.utils.SignInfo;
import com.itextpdf.io.codec.Base64;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.UnitValue;
import com.itextpdf.signatures.*;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.Streams;
import org.springframework.http.MediaType;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * @author Administrator
 */
@Slf4j
public class PdfService {

    private PdfProperties properties;

    public PdfService(PdfProperties pdfProperties) {
        this.properties = pdfProperties;
        log.info("pdf config info:{}",this.properties);
    }

    String fieldNameA = "SIGNATURE_A";
    String fieldNameB = "SIGNATURE_B";

    /**
     * 签名方法
     * @param pdfFileName pdf名称
     * @param pdfSignFileName 签名pdf名称
     */
    public void sign(String pdfFileName,String pdfSignFileName){
        sign(pdfFileName,"签章","北京八维",pdfSignFileName,properties.getX(),properties.getY(),properties.getWidth(),properties.getHeight());
    }
    /**
     * 签名方法
     * @param pdfFileName pdf名称
     * @param reason 签名说明
     * @param location 签名机构
     * @param pdfSignFileName 签名pdf名称
     * @param x 坐标：x
     * @param y 坐标：y
     * @param width 签名图片宽
     * @param height 签名图片高
     */
    public void sign(String pdfFileName,String reason,String location,String pdfSignFileName,int x,int y,int width,int height) {
        DeferredSignPdfAssLry signPdfAss = new DeferredSignPdfAssLry();
        Rectangle rectangle = new Rectangle(x, y, width, height);
        SignInfo signInfo = new SignInfo(rectangle,1,reason,location,fieldNameA,properties.getPath()+properties.getSignPicName(),true);
        //获取空的签名文件
        ExternalEmptySignatureContainer externalSignatureContainer = new ExternalEmptySignatureContainer(signPdfAss.getDicFilter(),signPdfAss.getDicSubfilter(),signPdfAss.getHashAlgorithm());
        //初始化
        try {
            signPdfAss.initSigner(Streams.readAll(new FileInputStream(properties.getPath()+pdfFileName)),properties.getPath()+pdfSignFileName+"_empty.pdf",signInfo,externalSignatureContainer);
        } catch (IOException | GeneralSecurityException e) {
            System.out.println("初始化签名对象异常");
            e.printStackTrace();
        }
        //待签名哈希,发给外部服务器进行签名
        String ToSignHash = Base64.encodeBytes(externalSignatureContainer.getDigestHash()) ;
        String signedHash = null;//cfca签名后的哈希值
        try {
            signedHash = signBase64(ToSignHash,signPdfAss.getHashAlgorithm());
        } catch (GeneralSecurityException e) {
            System.out.println("签名异常！");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("签名异常！");
            e.printStackTrace();
        }
        //验签，这一步需要通过外部服务器返回的signedHash，与原文件哈希值externalSignatureContainer.getDigestHash()进行验证

        byte[] cmsInfo = Base64.decode(signedHash);
        try {
            DeferredSignPdfAssLry.embedSignature(properties.getPath()+pdfSignFileName+"_empty.pdf",properties.getPath()+pdfSignFileName,fieldNameA,new EmbdedSignatureContainer(cmsInfo));
            //删除临时文件
            File file = new File(properties.getPath() + pdfSignFileName + "_empty.pdf");
            if (file.exists()) {
                file.delete();
            }
        } catch (IOException e) {
            System.out.println("分离签名异常！");
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            System.out.println("分离签名异常！");
            e.printStackTrace();
        }
    }

    /**
     * 电子签章
     * @param toSignHash
     * @param hashAlgorithm
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     */
    private String signBase64(String toSignHash,String hashAlgorithm) throws GeneralSecurityException, IOException {
        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(new FileInputStream(properties.getPdfKs()), properties.getPdfKsPass().toCharArray());
        String alias = ks.aliases().nextElement();
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKey pk = (PrivateKey) ks.getKey(alias, properties.getPdfKsPass().toCharArray());
        PrivateKeySignature signature = new PrivateKeySignature(pk, hashAlgorithm, "BC");
        BouncyCastleDigest digest = new BouncyCastleDigest();

        PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
        byte hash[] = Base64.decode(toSignHash);
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
        byte[] extSignature = signature.sign(sh);
        sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
        ITSAClient tsaClient = new TSAClientBouncyCastle(SignInfo.TSA_URL, "", "",4096, "SHA-512");
        byte[] pck7 =  sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CMS, tsaClient, null, null);
        return Base64.encodeBytes(pck7);
    }


    /**
     * 下载文件
     * @param fileName
     * @param response
     * @throws FileNotFoundException
     */
    public void download(String fileName, HttpServletResponse response) throws FileNotFoundException {
        String pdfPath = properties.getPath()+fileName;
        File file = new File(pdfPath);
        OutputStream os = null;
        try{
            FileInputStream in = new FileInputStream(file);
            BufferedInputStream bf = new BufferedInputStream(in);
            os = response.getOutputStream();
            response.setContentType(MediaType.APPLICATION_PDF_VALUE);
            response.setHeader("content-Disposition","attachment;filename="+fileName);
            byte[] b = new byte[bf.available() + 1000];
            int i;
            while((i=bf.read(b))!=-1){
                os.write(b,0,i);
            }
            os.flush();
        }catch(IOException exception){
            exception.printStackTrace();
        }finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * pdf预览
     * @param pdfName
     * @param response
     */
    public void preview(String pdfName, HttpServletResponse response) throws IOException {
        String filePath = properties.getPath() + pdfName;
        if(pdfName!=null){
            response.setContentType(MediaType.APPLICATION_PDF_VALUE);
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ServletOutputStream out = response.getOutputStream();
            byte[] buf = new byte[4096];
            int len = -1;
            while ((len = fis.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            out.flush();
            fis.close();
            out.close();
        }
    }

    /**
     * pdf导出
     * @param dataList
     * @param fileName
     * @param title
     * @param cls
     * @throws IOException
     */
    public <T> void export(List<T> dataList, String fileName, String title, Class<T> cls) throws IOException {
        try {
            //创建pdf对象
            PdfDocument pdf = new PdfDocument(new PdfWriter(properties.getPath()+fileName));
            //文档设置
            Document document = new Document(pdf);
            PdfFont headerFont = PdfFontFactory.createFont(StandardFonts.TIMES_BOLD);
            PdfFont cellFont = PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN);
            PdfFont font = PdfFontFactory.createFont(properties.getFont()+",0", PdfEncodings.IDENTITY_H,false);
            //读取PdfValue注解
            Field[] fields = cls.getDeclaredFields();
            List<PdfValue> pdfValues = new ArrayList<>();
            List<Field> fieldList = new ArrayList<>();
            for (Field field : fields) {
                PdfValue pdfValue = field.getDeclaredAnnotation(PdfValue.class);
                if (pdfValue != null) {
                    pdfValues.add(pdfValue);
                    fieldList.add(field);
                }
            }
            document.add(new Paragraph(title).setTextAlignment(TextAlignment.CENTER).setFont(font));
            //创建表格
            Table table = new Table(pdfValues.size());
            table.setWidth(UnitValue.createPercentValue(100));
            //生成表头
            pdfValues.forEach(pdfValue -> {
                table.addHeaderCell(new Cell(1, 1)
                        .setTextAlignment(TextAlignment.CENTER)
                        .setBackgroundColor(ColorConstants.LIGHT_GRAY)
                        .add(new Paragraph(pdfValue.colName())
                                .setFont(font)));
            });
            // adding rows
            dataList.forEach(data->{
                Map<String, Object> dataMap = BeanUtil.beanToMap(data);
                fieldList.forEach(field -> {
                    table.addCell(new Cell().add(new Paragraph(String.valueOf(dataMap.get(field.getName()))).setFont(font)));
                });
            });
            document.add(table);
            document.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
